<?php

namespace PHPSpring\Utils;

use PHPSpring\Core\AnnotationFactory;
use PHPSpring\Annotation\Interceptor;
use ReflectionClass;

class AnnotationParser {

  public static function parse ($clz) {
    $re = new ReflectionClass($clz);
    if ($re->isAbstract() || $re->isInterface() || $re->isTrait()) {
      return;
    }
    $clzAnnos = self::getAnnotations($re->getDocComment());

    $methodsAnnos = [];
    foreach ($re->getMethods() as $method) {
      $methodAnnos = self::getAnnotations($method->getDocComment());
      if (empty($methodAnnos)) {
        continue;
      }
      $params = $method->getParameters();
      $name = $method->getName();
      $paramsWithType = [];
      if (!empty($params)) {
        foreach ($params as $param) {
          $paramsWithType[] = [
            'name'     => $param->getName(),
            'type'     => Utils::typeToString($param->getType()),
            'default'  => ($param->isDefaultValueAvailable()) ? $param->getDefaultValue() : null,
            'optional' => $param->isOptional()
          ];
        }
      }
      $methodsAnnos[$name] = [
        'name'   => $name,
        'type'   => Utils::typeToString($method->getReturnType()),
        'annos'  => $methodAnnos,
        'params' => $paramsWithType
      ];
    }

    $propertiesAnnos = [];
    foreach ($re->getProperties() as $property) {
      $propertyAnnos = self::getAnnotations($property->getDocComment());
      if (empty($propertyAnnos)) {
        continue;
      }

      $propertiesAnnos[$property->getName()] = [
        'name'  => $property->getName(),
        'type'  => Utils::typeToString($property->getType()),
        'annos' => $propertyAnnos
      ];
    }

    if (!empty($clzAnnos) || !empty($methodsAnnos) || !empty($propertiesAnnos)) {
      $annos = [
        'class'      => [
          'name'  => $re->getShortName(),
          'type'  => $clz,
          'annos' => $clzAnnos],
        'methods'    => $methodsAnnos,
        'properties' => $propertiesAnnos];

      return $annos;
    }

    return;
  }

  private static function getAnnotations ($comment): ?array {
    if (empty($comment)) {
      return null;
    }
    $comment = trim(preg_replace('/^\/\*\*|\*\//', '', $comment));
    preg_match_all('/^\s*\**\s*(.*)/m', $comment, $matches);
    if (count($matches) < 2) {
      return null;
    }
    $hasBracket = false;
    $preProcessedComment = [];
    foreach ($matches[1] as $line) {
      if (!$hasBracket) {
        $annoStartPos = strpos($line, '@');
        if ($annoStartPos === false) {
          continue;
        }
        $line = substr($line, $annoStartPos);
        $hasBracket = strpos($line, '(') !== false;
      } else {
        $endBracketPos = strpos($line, ')');
        if ($endBracketPos !== false) {
          $hasBracket = false;
          $line = " ".substr($line, 0, $endBracketPos + 1);
        }
      }
      $preProcessedComment[] = $line;
    }

    if (empty($preProcessedComment)) {
      return null;
    }
    $comment = explode('@', preg_replace('/^@*|@*$/', '', implode('', $preProcessedComment)));
    $annos = [];
    foreach ($comment as $anno) {
      $funcSign = self::annoToFuncSign($anno);
      if (empty($funcSign) || !AnnotationFactory::exist($funcSign[0])) {
        continue;
      }
      $annoClz = AnnotationFactory::get($funcSign[0]);
      $interceptors = [];
      foreach (Interceptor::NAMES as $interceptor) {
        if (method_exists($annoClz, $interceptor)) {
          $interceptors[$interceptor] = true;
        }
      }
      $annos[$funcSign[0]] = [
        $annoClz,
        $funcSign[1],
        $interceptors
      ];
    }

    return $annos;
  }

  private static function annoToFuncSign ($anno) {
    preg_match('/^(\w+)(\(.*\))?/', $anno, $funcMatches);
    if (count($funcMatches) < 1) {
      return null;
    }
    $funcName = $funcMatches[1];
    $paramStr = count($funcMatches) > 2 ? $funcMatches[2] : '';
    if ($paramStr) {
      $paramStr = substr($paramStr, 1, -1);
    }
    $len = mb_strlen($paramStr);
    $params = [];
    $quote = '';
    $start = true;
    $param = '';
    $hasParentheses = false;
    for ($i = 0; $i < $len; $i++) {
      $ch = mb_substr($paramStr, $i, 1);
      if ($start) {
        if ($ch == ' ') {
          continue;
        }
        $start = false;
        if ($ch == '"' || $ch == "'") {
          $quote = $ch;
          continue;
        }
      } else {
        if (!empty($quote)) {
          if ($ch == $quote) {
            if (mb_substr($paramStr, $i - 1, 1) != '\\'
              && ($i == $len - 1 || mb_substr($paramStr, $i + 1, 1) == ',')) {
              $params[] = self::checkParamType($param);
              $start = true;
              $quote = '';
              $param = '';
              $i++;
              continue;
            }
          }
        } else {
          if ($hasParentheses) {
            if ($ch == ')') {
              $hasParentheses = false;
            }
            $param .= $ch;
            continue;
          } else {
            if ($ch == '(') {
              $hasParentheses = true;
              $param .= $ch;
              continue;
            }
          }
          if ($ch == ',') {
            $params[] = self::checkParamType($param);
            $start = true;
            $quote = '';
            $param = '';
            continue;
          }
        }
      }
      $param .= $ch;
    }
    if (!empty($param)) {
      $params[] = self::checkParamType($param);
    }

    return [$funcName, $params];
  }

  private static function checkParamType ($param) {
    $param = trim($param);
    if (is_numeric($param)) {
      $param = $param - 0;
    } elseif (is_string($param) && strlen($param) < 6) {
      $param1 = strtolower($param);
      if ($param1 == 'true') {
        $param = true;
      } elseif ($param1 == 'false') {
        $param = false;
      }
    }

    return $param;
  }
}
